Skip to Content

多级页表查询

准备工作:场景设定

  • 系统环境: 32位操作系统。
  • 页表结构: 二级页表。
  • 虚拟地址 (VA): 32位,结构如下: | 10位 页目录索引 (DIR) | 10位 页表索引 (TABLE) | 12位 页内偏移 (OFFSET) |
  • CPU中的关键部件:
    • MMU (Memory Management Unit): 内存管理单元,硬件电路,负责执行地址转换。
    • CR3 寄存器: CPU的一个特殊寄存器,存放着当前进程的页目录的物理基地址。每个进程切换时,操作系统会把新进程的页目录基地址加载到CR3中。
    • TLB (Translation Lookaside Buffer): 一个高速缓存,用于存储最近用过的虚拟地址到物理地址的映射,以加速查询。

查询流程:一次完整的地址转换之旅

假设现在CPU要执行一条指令,比如 MOV EAX, [0x12345678],即从虚拟地址 0x12345678 读取一个数据。

第 0 步:检查高速缓存 TLB (捷径)

这是MMU做的第一件事,也是最常发生的情况。

  1. MMU从虚拟地址 0x12345678 中提取出虚拟页号 (Virtual Page Number, VPN)。VPN就是去掉了页内偏移的部分,即 0x12345

  2. MMU拿着这个VPN (0x12345) 去查询TLB。

  3. 情况A:TLB 命中 (TLB Hit) - (大概率发生)

    • TLB中正好有 0x12345 对应的条目。
    • TLB直接返回缓存好的物理页框号 (Physical Page Frame, PFN),例如 0x87654
    • MMU将这个PFN和原始的12位页内偏移 0x678 拼接起来,形成最终的物理地址 0x87654678
    • 地址转换完成! 整个过程极快,因为TLB是高速硬件。
  4. 情况B:TLB 未命中 (TLB Miss) - (小概率发生)

    • TLB中没有 0x12345 的映射记录。
    • 此时,MMU必须启动“硬核”查询模式,即从内存中遍历多级页表。接下来的步骤都是在TLB Miss后发生的。

第 1 步:查询页目录 (一级查询)

MMU开始从内存中查找。

  1. 分解虚拟地址: MMU将虚拟地址 0x12345678 按照我们设定的结构进行分解。

    • 二进制表示: 0001 0010 0011 0100 0101 0110 0111 1000
    • 页目录索引 (DIR): 最高10位 -> 0001001000 (十进制为 72)
    • 页表索引 (TABLE): 中间10位 -> 1101000101 (十进制为 837)
    • 页内偏移 (OFFSET): 最低12位 -> 011001111000 (十进制为 1656,十六进制为 0x678)
  2. 定位页目录项 (PDE):

    • MMU从 CR3寄存器 中读取到当前进程的页目录的物理基地址。假设这个地址是 0x10000000
    • MMU计算PDE的物理地址: PDE地址 = 页目录基地址 + (DIR × 每个PDE的大小) PDE地址 = 0x10000000 + (72 × 4 Bytes) = 0x10000120
  3. 读取并解析PDE:

    • MMU向物理内存地址 0x10000120 发出请求,读取那4个字节的PDE内容。
    • MMU检查这个PDE中的有效位 (Present Bit)
      • 如果有效位为0 (无效): 说明该PDE对应的整个二级页表都不存在(即这4MB的虚拟地址空间从未使用过)。MMU会立即停止,并触发一个缺页异常 (Page Fault)。操作系统内核接管,可能会去分配一个新的二级页表,然后再返回用户态继续执行。
      • 如果有效位为1 (有效): 太好了!PDE是有效的。MMU从这个PDE中提取出二级页表的物理基地址。这个地址占据了PDE中的大部分位。假设提取出的地址是 0x11220000

第 2 步:查询二级页表 (二级查询)

现在我们有了二级页表的地址,可以进行下一步了。

  1. 定位页表项 (PTE):

    • MMU使用上一步分解出的页表索引 (TABLE),即 837
    • MMU计算PTE的物理地址: PTE地址 = 二级页表基地址 + (TABLE × 每个PTE的大小) PTE地址 = 0x11220000 + (837 × 4 Bytes) = 0x11220D14
  2. 读取并解析PTE:

    • MMU向物理内存地址 0x11220D14 发出请求,读取那4个字节的PTE内容。
    • MMU再次检查这个PTE中的有效位 (Present Bit)
      • 如果有效位为0 (无效): 说明这个虚拟页面本身不在物理内存中(可能从未被使用,或被换出到硬盘了)。MMU同样触发一个缺页异常 (Page Fault)。操作系统接管,可能会从硬盘把页面读回内存,或者分配一个新页面。
      • 如果有效位为1 (有效): 成功在望!PTE是有效的。MMU从这个PTE中提取出最终的物理页框号 (PFN)。假设提取出的PFN是 0x87654

第 3 步:合成最终物理地址

  1. 拼接地址:

    • MMU将上一步得到的物理页框号 (PFN) 0x87654 作为高位。
    • MMU将第一步分解出的页内偏移 (OFFSET) 0x678 作为低位。
    • 拼接成最终的物理地址: 0x87654 (PFN) + 678 (OFFSET) = 0x87654678
  2. 访问数据:

    • MMU将这个物理地址 0x87654678 交给总线,内存控制器会从这个地址读取数据,并返回给CPU的寄存器EAX。

第 4 步:更新TLB (后续优化)

在成功完成这次“硬核”查询后,MMU会把这次的映射关系缓存起来,以备后用。

  • Key: 虚拟页号 (VPN) 0x12345
  • Value: 物理页框号 (PFN) 0x87654,以及相关的权限位等。

MMU将这个键值对写入TLB。这样,下一次CPU再访问 0x12345xxx 这个虚拟页内的任何地址时,就会直接TLB命中,跳过上面所有复杂的内存查询步骤。


扩展到64位四级页表

这个流程可以很自然地扩展。对于64位系统的四级页表,虚拟地址会被分解成更多部分:

| L4索引 | L3索引 | L2索引 | L1索引 | 页内偏移 |

查询流程就变成了:

  1. TLB Miss后…
  2. 查CR3,用L4索引找到L4表项,得到L3表的基地址。
  3. 查L3表,用L3索引找到L3表项,得到L2表的基地址。
  4. 查L2表,用L2索引找到L2表项,得到L1表的基地址。
  5. 查L1表,用L1索引找到L1表项,得到最终的物理页框号(PFN)
  6. 拼接PFN和页内偏移,得到物理地址。
  7. 更新TLB。

虽然步骤变多了,但每一步的原理和逻辑是完全一样的:用索引定位表项,从表项中获取下一级表的基地址,直到最后一级获取到物理页框号。每一次内存访问都可能触发缺页异常。这就是多级页表查询的精髓所在。

Last updated on